1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module code_checker.types;
7 
8 @safe:
9 
10 /// No guarantee regarding the path. May be absolute, relative, contain a '~'.
11 /// The user of this type must do all the safety checks to ensure that the
12 /// datacontained in valid.
13 struct Path {
14     string payload;
15     alias payload this;
16 }
17 
18 /// ditto
19 struct DirName {
20     Path payload;
21     alias payload this;
22 
23     pure nothrow @nogc this(string p) {
24         payload = Path(p);
25     }
26 }
27 
28 /// ditto
29 struct FileName {
30 pure @nogc nothrow:
31 
32     Path payload;
33     alias payload this;
34 
35     this(Path p) {
36         payload = p;
37     }
38 
39     pure nothrow @nogc this(string p) {
40         payload = Path(p);
41     }
42 }
43 
44 /** The path is guaranteed to be the absolute path.
45  *
46  * The user of the type has to make an explicit judgment when using the
47  * assignment operator. Either a `Path` and then pay the cost of the path
48  * expansion or an absolute which is already assured to be _ok_.
49  * This divides the domain in two, one unchecked and one checked.
50  */
51 struct AbsolutePath {
52     import std.path : expandTilde, buildNormalizedPath;
53 
54     Path payload;
55     alias payload this;
56 
57     invariant {
58         import std.path : isAbsolute;
59 
60         assert(payload.length == 0 || payload.isAbsolute);
61     }
62 
63     this(AbsolutePath p) {
64         this.payload = p.payload;
65     }
66 
67     this(Path p) {
68         auto p_expand = () @trusted{ return p.expandTilde; }();
69         // the second buildNormalizedPath is needed to correctly resolve "."
70         // otherwise it is resolved to /foo/bar/.
71         payload = buildNormalizedPath(p_expand).toRealPath;
72     }
73 
74     /// Build the normalised path from workdir.
75     this(FileName p, DirName workdir) {
76         auto p_expand = () @trusted{ return p.expandTilde; }();
77         auto workdir_expand = () @trusted{ return workdir.expandTilde; }();
78         // the second buildNormalizedPath is needed to correctly resolve "."
79         // otherwise it is resolved to /foo/bar/.
80         payload = buildNormalizedPath(workdir_expand, p_expand).toRealPath;
81     }
82 
83     void opAssign(Path p) {
84         payload = p;
85     }
86 
87     void opAssign(AbsolutePath p) pure nothrow @nogc {
88         payload = p.payload;
89     }
90 
91     FileName opCast(T : FileName)() pure nothrow @nogc {
92         return FileName(payload);
93     }
94 
95     Path opCast(T : Path)() @safe pure nothrow const @nogc scope {
96         return payload;
97     }
98 
99     string opCast(T : string)() pure nothrow @nogc {
100         return payload;
101     }
102 }
103 
104 struct AbsoluteFileName {
105     AbsolutePath payload;
106     alias payload this;
107 
108     pure nothrow @nogc this(AbsolutePath p) {
109         payload = p;
110     }
111 }
112 
113 struct AbsoluteDirectory {
114     AbsolutePath payload;
115     alias payload this;
116 
117     pure nothrow @nogc this(AbsolutePath p) {
118         payload = p;
119     }
120 }
121 
122 /** During construction checks that the file exists on the filesystem.
123  *
124  * If it doesn't exist it will throw an Exception.
125  */
126 struct Exists(T) {
127     AbsolutePath payload;
128     alias payload this;
129 
130     this(AbsolutePath p) {
131         import std.file : exists, FileException;
132 
133         if (!exists(p)) {
134             throw new FileException("File do not exist: " ~ cast(string) p);
135         }
136 
137         payload = p;
138     }
139 
140     this(Exists!T p) {
141         payload = p.payload;
142     }
143 
144     void opAssign(Exists!T p) pure nothrow @nogc {
145         payload = p;
146     }
147 }
148 
149 auto makeExists(T)(T p) {
150     return Exists!T(p);
151 }
152 
153 @("shall always be the absolute path")
154 unittest {
155     import std.algorithm : canFind;
156     import std.path;
157     import unit_threaded;
158 
159     AbsolutePath(FileName("~/foo")).canFind('~').shouldEqual(false);
160     AbsolutePath(FileName("foo")).isAbsolute.shouldEqual(true);
161 }
162 
163 @("shall expand . without any trailing /.")
164 unittest {
165     import std.algorithm : canFind;
166     import unit_threaded;
167 
168     AbsolutePath(FileName(".")).canFind('.').shouldBeFalse;
169     AbsolutePath(FileName("."), DirName(".")).canFind('.').shouldBeFalse;
170 }
171 
172 @("shall be an instantiation of Exists")
173 nothrow unittest {
174     // the file is not expected to exist.
175 
176     try {
177         auto p = makeExists(AbsolutePath(FileName("foo")));
178     } catch (Exception e) {
179     }
180 }
181 
182 private:
183 
184 /** Convert a string to the "real path" by resolving all symlinks resulting in an absolute path.
185 
186 TODO: optimize
187 This function is very inefficient. It creates a lot of GC garbage.
188 
189 trusted: orig_p is a string. A string is assured by the language to be memory
190 safe. Thus this function that operates on strings as input are memory safe for
191 all possible input.
192   */
193 Path toRealPath(const string orig_p) @trusted {
194     import std.conv : to;
195     import std.path : asAbsolutePath, asNormalizedPath;
196 
197     version (Windows) {
198         return path.asAbsolutePath.asNormalizedPath.to!string.Path;
199     } else {
200         import core.sys.posix.stdlib : realpath;
201         import core.stdc.stdlib : free;
202         import std..string : toStringz, fromStringz;
203 
204         auto p = orig_p.toStringz;
205         auto absp = realpath(p, null);
206         scope (exit) {
207             if (absp)
208                 free(absp);
209         }
210 
211         if (absp is null)
212             return orig_p.asAbsolutePath.asNormalizedPath.to!string.Path;
213         else
214             return absp.fromStringz.idup.Path;
215     }
216 }